package com.itqf.controller;

import com.itqf.bean.DtsAdmin;
import com.itqf.captcha.CaptchaCodeManager;
import com.itqf.constants.AdminConstant;
import com.itqf.service.DtsPermissionService;
import com.itqf.service.DtsRoleService;
import com.itqf.util.Base64;
import com.itqf.util.ResponseUtil;
import com.itqf.util.UUID;
import com.itqf.utils.*;
import com.itqf.vo.LoginVo;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.WebApplicationContext;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/**
 * projectName: store_parent
 *
 * @author: 赵伟风
 * time: 2021/6/8 14:03
 * description: 认证资源路径
 * 501代表的请求登录!
 * 506代表的无权限!
 * <p>
 * ResponseUtil
 * errno:
 * errmsg:
 * data:
 */
@CrossOrigin("*")
@RequestMapping("/admin/auth/")
@RestController
public class AuthController {


    Logger logger = LoggerFactory.getLogger(AuthController.class);

    @Autowired
    private DtsRoleService roleService;

    @Autowired
    private DtsPermissionService permissionService;

    @Autowired
    private WebApplicationContext applicationContext;

    @GetMapping("501")
    public Object ret501() {
        //[本次动作] 描述即可!
        logger.info("[认证未通过]认证未通过!需要返回状态码501,前端跳转到登录页面!");

        return ResponseUtil.unlogin();
    }


    @GetMapping("506")
    public Object ret506() {
        //[本次动作] 描述即可!
        logger.info("[授权未通过]授权未通过!需要返回状态码506,前端跳转到权限显示页面!");

        return ResponseUtil.unauthz();
    }


    @GetMapping("index")
    public Object retIndex() {
        //[本次动作] 描述即可!
        logger.info("[认证通过]授权通过!前端跳转到登录页面!");

        return ResponseUtil.ok();
    }


    /**
     * 验证码的生成
     * CaptchaCodeManager 验证码缓存
     * 课下阅读下自定义的缓存实现!
     * CaptchaItem   uid code 时间
     * VerifyCodeUtils 生成验证码和验证码图片
     * Base64Utils 图片转成Base64的
     */
    @GetMapping("captchaImage")
    public Object createImage() throws IOException {

        logger.info("[验证码生成] 验证码生成接口开始触发!");
        //1.生成验证码
        String verifyCode = VerifyCodeUtils.generateVerifyCode(4);
        //2.生成一个随机码
        String uuidStr = UUID.randomUUID().toString(true);
        //3.将验证码和随机码进行缓存
        boolean cache = CaptchaCodeManager.addToCache(uuidStr, verifyCode, 10);

        if (!cache) {
            logger.error("[验证码生成] 发生异常! {}验证码对应的缓存数据已经存在,请重新生成!", verifyCode);

            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_ERROR);
        }
        //4.验证码图片生成
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        VerifyCodeUtils.outputImage(AdminConstant.IMG_WIDTH, AdminConstant.IMG_HEIGHT, baos, verifyCode);

        //5.封装data
        Map<String, String> data = new HashMap<>();
        //将验证码对应uuid返回给前端!
        data.put("uuid", uuidStr);

        //将图片存到的输出流转成字节数组,在将字节数组转成base64字符串
        String imgBase64Str = Base64.encode(baos.toByteArray());
        data.put("img", imgBase64Str);

        //6.拼接完整的返回数据
        Object ok = ResponseUtil.ok(data);

        logger.info("[验证码生成] 验证码生成接口执行完毕! 生成验证码为:{}!", verifyCode);

        System.out.println("ok = " + ok);

        return ok;
    }


    @PostMapping("login")
    public Object login(@RequestBody LoginVo loginVo) {

        logger.info("[认证] 认证开始!认证数据为:{}!", loginVo);

        //1.参数的验证

        if (StringUtils.isEmpty(loginVo.getUsername()) || StringUtils.isEmpty(loginVo.getPassword()) ||
                StringUtils.isEmpty(loginVo.getCode()) || StringUtils.isEmpty(loginVo.getUuid())) {
            logger.error("[认证] 认证失败! 传递的参数为null!");
            return ResponseUtil.badArgument();
        }

        //2.获取缓存验证码
        String code = CaptchaCodeManager.getCachedCaptcha(loginVo.getUuid());
        if (code == null) {
            logger.error("[认证] 认证失败! {}对应的验证码失效!",loginVo.getUuid());
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_EXPIRED);
        }
        //3.验证码对比

        if (!code.equalsIgnoreCase(loginVo.getCode())) {
            logger.error("[认证] 认证失败! 用户输入的验证码:{}和正确的验证码{}不匹配!",loginVo.getCode(),code);
            return AdminResponseUtil.fail(AdminResponseCode.AUTH_CAPTCHA_ERROR);
        }

        //4.认证验证
        Subject subject = null;
        try {
            subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken(loginVo.getUsername(),loginVo.getPassword() );
            subject.login(token);
        }catch (AuthenticationException e){
            logger.error("[认证] 认证失败! 用户输入的账号:{}和密码:{}不正确!",loginVo.getUsername(),loginVo.getPassword());
            return AdminResponseUtil.fail(AdminResponseCode.ADMIN_INVALID_AUTH);
        }
        //5.响应数据处理
        //获取唯一标签
        Serializable id = subject.getSession().getId();

        return ResponseUtil.ok(id);
    }



    @RequiresAuthentication
    @PostMapping("logout")
    public Object logout() {

        logger.info("[退出登录] 接口开始调用!");
        Subject subject = SecurityUtils.getSubject();
        subject.logout();

        logger.info("[退出登录] 接口结束调用!");

        return ResponseUtil.ok();
    }




    /**
     * 认证方可访问
     *    用户名
     *    头像
     *    角色 []
     *    权限 []
     * @return
     */
    @RequiresAuthentication
    @GetMapping(value = "info" )
    public Object info(){

        logger.info("[用户信息] 接口开始进行调用!");

        //获取你认证过的subject
        Subject subject = SecurityUtils.getSubject();
        DtsAdmin admin = (DtsAdmin) subject.getPrincipal();

        //制造data对应map
        Map<String,Object> data = new HashMap<>();
        data.put("name", admin.getUsername());
        data.put("avatar", admin.getAvatar());

        //查询角色
        Collection<String> roleNames = roleService.findRoleNames(admin.getRoleIds());
        Object[] rnames = roleNames.toArray();
        data.put("roles",rnames);

        //权限
        /**
         *
         *  roles:[a,c]
         *  perms:[*  |  post 路径]
         *     前端是根据权限显示菜单!
         *     * 显示所有菜单
         *
         *     ["请求方式 路径","get /admin/admin/list","post /admin/admin/delete"]
         *
         *     需求1: 读取项目中所有的 handler的请求方式和路径   方式 路径
         *              handler的路径:  反射: 指定包名,包名中的所有类对象! -  Class methods();
         *                             类-获取 @RequestMapping -- value() +  handler路径 --获取 @GetMapping/PostMapping -- value();
         *                            类型Mapping  GET/POST /admin/auth/info
         *
         *                            要求:handler不能使用 @requestmapping 必须使用 @GetMapping/PostMapping
         *
         *                            post /xx/xx/xx
         *                            get /xx/xx/xx
         *                            delete /xx/xxx/x/xx
         *
         *     需求2: 我只想返回,我拥有权限的路径!
         *
         */
        //拥有的权限
        //我拥有的权限
        Collection<String> permissionNames = permissionService.findPermissionNames(admin.getRoleIds());

        String basePackage = "com.itqf.controller";
        /**
         * 参数1:全局工具类 web的ioc容器
         * 参数2:指明扫描的包
         * permissionList 全部的路径  post /xx/xx 和 对应的权限(权限注解)
         */
        List<Permission> permissionList = PermissionUtil.listPermission(applicationContext, basePackage);


        //最后返回的集合
        Set<String> perms = new HashSet<>();

        //做*判断
        if (permissionNames.contains("*")) {
            //权限中有* 直接返回* 前端显示所有数据!
            perms.add("*");
        }else{
            //没有* 判断对应权限的路径返回!
            for (Permission permission : permissionList) {
                //路径对应的正确权限
                String pers = permission.getRequiresPermissions().value()[0];
                //判断我是否有路径对应的权限
                if (permissionNames.contains(pers)) {
                    perms.add(permission.getApi()); //获取对应的路径 "post /xx/xx"
                }
            }
        }


        data.put("perms", perms.toArray());


        logger.info("[用户信息]装配数据完毕! 数据为:{}", data);

        return ResponseUtil.ok(data);
    }

}
